1 <?php
2
3 define('datalist_filters_count', 20);
4 define('datalist_image_uploads_exist', True);
5 define('datalist_max_records_multi_selection', 1000);
6 define('datalist_max_page_lump', 50);
7 define('datalist_max_records_dv_print', 100);
8 define('datalist_auto_complete_size', 1000);
9 define('datalist_date_separator', '/');
10 define('datalist_date_format', 'mdY');
11
12 $curr_dir = dirname(__FILE__);
13 require_once($curr_dir . '/combo.class.php');
14 require_once($curr_dir . '/data_combo.class.php');
15 require_once($curr_dir . '/date_combo.class.php');
16
17 class DataList{
18 // this class generates the data table ...
19
20 var $QueryFieldsTV,
21 $QueryFieldsCSV,
22 $QueryFieldsFilters,
23 $QueryFieldsQS,
24 $QueryFrom,
25 $QueryWhere,
26 $QueryOrder,
27 $filterers,
28
29 $ColWidth, // array of field widths
30 $DataHeight,
31 $TableName,
32
33 $AllowSelection,
34 $AllowDelete,
35 $AllowMassDelete,
36 $AllowDeleteOfParents,
37 $AllowInsert,
38 $AllowUpdate,
39 $SeparateDV,
40 $Permissions,
41 $AllowFilters,
42 $AllowSavingFilters,
43 $AllowSorting,
44 $AllowNavigation,
45 $AllowPrinting,
46 $HideTableView,
47 $AllowCSV,
48 $CSVSeparator,
49
50 $QuickSearch, // 0 to 3
51
52 $RecordsPerPage,
53 $ScriptFileName,
54 $RedirectAfterInsert,
55 $TableTitle,
56 $PrimaryKey,
57 $DefaultSortField,
58 $DefaultSortDirection,
59
60 // Templates variables
61 $Template,
62 $SelectedTemplate,
63 $TemplateDV,
64 $TemplateDVP,
65 $ShowTableHeader, // 1 = show standard table headers
66 $ShowRecordSlots, // 1 = show empty record slots in table view
67 $TVClasses,
68 $DVClasses,
69 // End of templates variables
70
71 $ContentType, // set by DataList to 'tableview', 'detailview', 'tableview+detailview', 'print-tableview', 'print-detailview' or 'filters'
72 $HTML; // generated html after calling Render()
73
74 function __construct(){ // PHP 7 compatibility
75 $this->DataList();
76 }
77
78 function DataList(){ // Constructor function
79 $this->DataHeight = 150;
80
81 $this->AllowSelection = 1;
82 $this->AllowDelete = 1;
83 $this->AllowInsert = 1;
84 $this->AllowUpdate = 1;
85 $this->AllowFilters = 1;
86 $this->AllowNavigation = 1;
87 $this->AllowPrinting = 1;
88 $this->HideTableView = 0;
89 $this->QuickSearch = 0;
90 $this->AllowCSV = 0;
91 $this->CSVSeparator = ",";
92 $this->HighlightColor = '#FFF0C2'; // default highlight color
93
94 $this->RecordsPerPage = 10;
95 $this->Template = '';
96 $this->HTML = '';
97 $this->filterers = array();
98 }
99
100 function showTV(){
101 if($this->SeparateDV){
102 $this->HideTableView = ($this->Permissions[2]==0 ? 1 : 0);
103 }
104 }
105
106 function hideTV(){
107 if($this->SeparateDV){
108 $this->HideTableView = 1;
109 }
110 }
111
112 function Render(){
113 // get post and get variables
114 global $Translation;
115
116 $adminConfig = config('adminConfig');
117
118 $FiltersPerGroup = 4;
119 $buttonWholeWidth = 136;
120
121 $current_view = ''; /* TV, DV, TVDV, TVP, DVP, Filters */
122
123 $Embedded = intval($_REQUEST['Embedded']);
124 $AutoClose = intval($_REQUEST['AutoClose']);
125
126 $SortField = $_REQUEST["SortField"];
127 $SortDirection = $_REQUEST["SortDirection"];
128 $FirstRecord = $_REQUEST["FirstRecord"];
129 $ScrollUp_y = $_REQUEST["ScrollUp_y"];
130 $ScrollDn_y = $_REQUEST["ScrollDn_y"];
131 $Previous_x = $_REQUEST["Previous_x"];
132 $Next_x = $_REQUEST["Next_x"];
133 $Filter_x = $_REQUEST["Filter_x"];
134 $SaveFilter_x = $_REQUEST["SaveFilter_x"];
135 $NoFilter_x = $_REQUEST["NoFilter_x"];
136 $CancelFilter = $_REQUEST["CancelFilter"];
137 $ApplyFilter = $_REQUEST["ApplyFilter"];
138 $Search_x = $_REQUEST["Search_x"];
139 $SearchString = (get_magic_quotes_gpc() ? stripslashes($_REQUEST['SearchString']) : $_REQUEST['SearchString']);
140 $CSV_x = $_REQUEST["CSV_x"];
141
142 $FilterAnd = $_REQUEST["FilterAnd"];
143 $FilterField = $_REQUEST["FilterField"];
144 $FilterOperator = $_REQUEST["FilterOperator"];
145 if(is_array($_REQUEST['FilterValue'])){
146 foreach($_REQUEST['FilterValue'] as $fvi=>$fv){
147 $FilterValue[$fvi]=(get_magic_quotes_gpc() ? stripslashes($fv) : $fv);
148 }
149 }
150
151 $Print_x = $_REQUEST['Print_x'];
152 $PrintTV = $_REQUEST['PrintTV'];
153 $PrintDV = $_REQUEST['PrintDV'];
154 $SelectedID = (get_magic_quotes_gpc() ? stripslashes($_REQUEST['SelectedID']) : $_REQUEST['SelectedID']);
155 $insert_x = $_REQUEST['insert_x'];
156 $update_x = $_REQUEST['update_x'];
157 $delete_x = $_REQUEST['delete_x'];
158 $SkipChecks = $_REQUEST['confirmed'];
159 $deselect_x = $_REQUEST['deselect_x'];
160 $addNew_x = $_REQUEST['addNew_x'];
161 $dvprint_x = $_REQUEST['dvprint_x'];
162 $DisplayRecords = (in_array($_REQUEST['DisplayRecords'], array('user', 'group')) ? $_REQUEST['DisplayRecords'] : 'all');
163
164 $mi = getMemberInfo();
165
166 // insure authenticity of user inputs:
167 if(is_array($FilterAnd)){
168 foreach($FilterAnd as $i => $f){
169 if($f && !preg_match('/^(and|or)$/i', trim($f))){
170 $FilterAnd[$i] = 'and';
171 }
172 }
173 }
174 if(is_array($FilterField)){
175 foreach($FilterField as $ffi => $ffn){
176 $FilterField[$ffi] = intval($ffn);
177 }
178 }
179 if(is_array($FilterOperator)){
180 foreach($FilterOperator as $i => $f){
181 if($f && !in_array(trim($f), array_keys($GLOBALS['filter_operators']))){
182 $FilterOperator[$i] = '';
183 }
184 }
185 }
186 if(!preg_match('/^\s*[1-9][0-9]*\s*(asc|desc)?(\s*,\s*[1-9][0-9]*\s*(asc|desc)?)*$/i', $SortField)){
187 $SortField = '';
188 }
189 if(!preg_match('/^(asc|desc)$/i', $SortDirection)){
190 $SortDirection = '';
191 }
192
193 if(!$this->AllowDelete){
194 $delete_x = '';
195 }
196 if(!$this->AllowDeleteOfParents){
197 $SkipChecks = '';
198 }
199 if(!$this->AllowInsert){
200 $insert_x = '';
201 $addNew_x = '';
202 }
203 if(!$this->AllowUpdate){
204 $update_x = '';
205 }
206 if(!$this->AllowFilters){
207 $Filter_x = '';
208 }
209 if(!$this->AllowPrinting){
210 $Print_x = '';
211 $PrintTV = '';
212 }
213 if(!$this->QuickSearch){
214 $SearchString = '';
215 }
216 if(!$this->AllowCSV){
217 $CSV_x = '';
218 }
219
220 // enforce record selection if user has edit/delete permissions on the current table
221 $AllowPrintDV=1;
222 $this->Permissions=getTablePermissions($this->TableName);
223 if($this->Permissions[3] || $this->Permissions[4]){ // current user can edit or delete?
224 $this->AllowSelection = 1;
225 }elseif(!$this->AllowSelection){
226 $SelectedID='';
227 $AllowPrintDV=0;
228 $PrintDV='';
229 }
230
231 if(!$this->AllowSelection || !$SelectedID){ $dvprint_x=''; }
232
233 $this->QueryFieldsIndexed=reIndex($this->QueryFieldsFilters);
234
235 // determine type of current view: TV, DV, TVDV, TVP, DVP or Filters?
236 if($this->SeparateDV){
237 $current_view = 'TV';
238 if($Print_x != '' || $PrintTV != '') $current_view = 'TVP';
239 elseif($dvprint_x != '' || $PrintDV != '') $current_view = 'DVP';
240 elseif($Filter_x != '') $current_view = 'Filters';
241 elseif(($SelectedID && !$deselect_x && !$delete_x) || $addNew_x != '') $current_view = 'DV';
242 }else{
243 $current_view = 'TVDV';
244 if($Print_x != '' || $PrintTV != '') $current_view = 'TVP';
245 elseif($dvprint_x != '' || $PrintDV != '') $current_view = 'DVP';
246 elseif($Filter_x != '') $current_view = 'Filters';
247 }
248
249 $this->HTML .= '<div class="row"><div class="col-xs-11 col-md-12">';
250 $this->HTML .= '<form ' . (datalist_image_uploads_exist ? 'enctype="multipart/form-data" ' : '') . 'method="post" name="myform" action="' . $this->ScriptFileName . '">';
251 if($Embedded) $this->HTML .= '<input name="Embedded" value="1" type="hidden">';
252 if($AutoClose) $this->HTML .= '<input name="AutoClose" value="1" type="hidden">';
253 $this->HTML .= '<script>';
254 $this->HTML .= 'function enterAction(){';
255 $this->HTML .= ' if($j("input[name=SearchString]:focus").length){ $j("#Search").click(); }';
256 $this->HTML .= ' return false;';
257 $this->HTML .= '}';
258 $this->HTML .= '</script>';
259 $this->HTML .= '<input id="EnterAction" type="submit" style="position: absolute; left: 0px; top: -250px;" onclick="return enterAction();">';
260
261 $this->ContentType='tableview'; // default content type
262
263 if($PrintTV != ''){
264 $Print_x = 1;
265 $_REQUEST['Print_x'] = 1;
266 }
267
268 // handle user commands ...
269 if($deselect_x != ''){
270 $SelectedID = '';
271 $this->showTV();
272 }
273
274 elseif($insert_x != ''){
275 $SelectedID = call_user_func($this->TableName.'_insert');
276
277 // redirect to a safe url to avoid refreshing and thus
278 // insertion of duplicate records.
279 $url = $this->RedirectAfterInsert;
280 $insert_status = 'record-added-ok=' . rand();
281 if(!$SelectedID) $insert_status = 'record-added-error=' . rand();
282
283 // compose filters and sorting
284 foreach($this->filterers as $filterer => $caption){
285 if($_REQUEST['filterer_' . $filterer] != '') $filtersGET .= '&filterer_' . $filterer . '=' . urlencode($_REQUEST['filterer_' . $filterer]);
286 }
287 for($i = 1; $i <= (20 * $FiltersPerGroup); $i++){ // Number of filters allowed
288 if($FilterField[$i] != '' && $FilterOperator[$i] != '' && ($FilterValue[$i] != '' || strpos($FilterOperator[$i], 'empty'))){
289 $filtersGET .= "&FilterAnd[{$i}]={$FilterAnd[$i]}&FilterField[{$i}]={$FilterField[$i]}&FilterOperator[{$i}]={$FilterOperator[$i]}&FilterValue[{$i}]=" . urlencode($FilterValue[$i]);
290 }
291 }
292 if($Embedded) $filtersGET .= '&Embedded=1&SelectedID=' . urlencode($SelectedID);
293 if($AutoClose) $filtersGET .= '&AutoClose=1';
294 $filtersGET .= "&SortField={$SortField}&SortDirection={$SortDirection}&FirstRecord={$FirstRecord}";
295 $filtersGET .= "&DisplayRecords={$DisplayRecords}";
296 $filtersGET .= '&SearchString=' . urlencode($SearchString);
297 $filtersGET = substr($filtersGET, 1); // remove initial &
298
299 if($url){
300 /* if designer specified a redirect-after-insert url */
301 $url .= (strpos($url, '?') !== false ? '&' : '?') . $insert_status;
302 $url .= (strpos($url, $this->ScriptFileName) !== false ? "&{$filtersGET}" : '');
303 $url = str_replace("#ID#", urlencode($SelectedID), $url);
304 }else{
305 /* if no redirect-after-insert url, use default */
306 $url = "{$this->ScriptFileName}?{$insert_status}&{$filtersGET}";
307
308 /* if DV and TV in same page, select new record */
309 if(!$this->SeparateDV) $url .= '&SelectedID=' . urlencode($SelectedID);
310 }
311
312 @header('Location: ' . $url);
313 $this->HTML .= "<META HTTP-EQUIV=\"Refresh\" CONTENT=\"0;url=" . $url ."\">";
314
315 return;
316 }
317
318 elseif($delete_x != ''){
319 $delete_res = call_user_func($this->TableName.'_delete', $SelectedID, $this->AllowDeleteOfParents, $SkipChecks);
320 // handle ajax delete requests
321 if(is_ajax()){
322 die($delete_res ? $delete_res : 'OK');
323 }
324
325 if($delete_res){
326 //$_REQUEST['record-deleted-error'] = 1;
327 $this->HTML .= showNotifications($delete_res, 'alert alert-danger', false);
328 $this->hideTV();
329 $current_view = ($this->SeparateDV ? 'DV' : 'TVDV');
330 }else{
331 $_REQUEST['record-deleted-ok'] = 1;
332 $SelectedID = '';
333 $this->showTV();
334
335 /* close window if embedded */
336 if($Embedded){
337 $this->HTML .= '<script>$j(function(){ setTimeout(function(){ window.parent.jQuery(".modal").modal("hide"); }, 2000); })</script>';
338 }
339 }
340 }
341
342 elseif($update_x != ''){
343 $updated = call_user_func($this->TableName.'_update', $SelectedID);
344
345 $update_status = 'record-updated-ok=' . rand();
346 if($updated === false) $update_status = 'record-updated-error=' . rand();
347
348 // compose filters and sorting
349 foreach($this->filterers as $filterer => $caption){
350 if($_REQUEST['filterer_' . $filterer] != '') $filtersGET .= '&filterer_' . $filterer . '=' . urlencode($_REQUEST['filterer_' . $filterer]);
351 }
352 for($i = 1; $i <= (20 * $FiltersPerGroup); $i++){ // Number of filters allowed
353 if($FilterField[$i] != '' && $FilterOperator[$i] != '' && ($FilterValue[$i] != '' || strpos($FilterOperator[$i], 'empty'))){
354 $filtersGET .= "&FilterAnd[{$i}]={$FilterAnd[$i]}&FilterField[{$i}]={$FilterField[$i]}&FilterOperator[{$i}]={$FilterOperator[$i]}&FilterValue[{$i}]=" . urlencode($FilterValue[$i]);
355 }
356 }
357 $filtersGET .= "&SortField={$SortField}&SortDirection={$SortDirection}&FirstRecord={$FirstRecord}&Embedded={$Embedded}";
358 if($AutoClose) $filtersGET .= '&AutoClose=1';
359 $filtersGET .= "&DisplayRecords={$DisplayRecords}";
360 $filtersGET .= '&SearchString=' . urlencode($SearchString);
361 $filtersGET = substr($filtersGET, 1); // remove initial &
362
363 $redirectUrl = $this->ScriptFileName . '?SelectedID=' . urlencode($SelectedID) . '&' . $filtersGET . '&' . $update_status;
364 @header("Location: $redirectUrl");
365 $this->HTML .= '<META HTTP-EQUIV="Refresh" CONTENT="0;url='.$redirectUrl.'">';
366 return;
367 }
368
369 elseif($addNew_x != ''){
370 $SelectedID='';
371 $this->hideTV();
372 }
373
374 elseif($Print_x != ''){
375 // print code here ....
376 $this->AllowNavigation = 0;
377 $this->AllowSelection = 0;
378 }
379
380 elseif($SaveFilter_x != '' && $this->AllowSavingFilters){
381 $filter_link = $_SERVER['HTTP_REFERER'] . '?SortField=' . urlencode($SortField) . '&SortDirection=' . $SortDirection . '&';
382 for($i = 1; $i <= (20 * $FiltersPerGroup); $i++){ // Number of filters allowed
383 if(($FilterField[$i] != '' || $i == 1) && $FilterOperator[$i] != '' && ($FilterValue[$i] != '' || strpos($FilterOperator[$i], 'empty'))){
384 $filter_link .= urlencode("FilterAnd[$i]") . '=' . urlencode($FilterAnd[$i]) . '&';
385 $filter_link .= urlencode("FilterField[$i]") . '=' . urlencode($FilterField[$i]) . '&';
386 $filter_link .= urlencode("FilterOperator[$i]") . '=' . urlencode($FilterOperator[$i]) . '&';
387 $filter_link .= urlencode("FilterValue[$i]") . '=' . urlencode($FilterValue[$i]) . '&';
388 }elseif(($i % $FiltersPerGroup == 1) && in_array($FilterAnd[$i], array('and', 'or'))){
389 /* always include the and/or at the beginning of each group */
390 $filter_link .= urlencode("FilterAnd[$i]") . '=' . urlencode($FilterAnd[$i]) . '&';
391 }
392 }
393 $filter_link = substr($filter_link, 0, -1); /* trim last '&' */
394
395 $this->HTML .= '<div id="saved_filter_source_code" class="row"><div class="col-md-6 col-md-offset-3">';
396 $this->HTML .= '<div class="panel panel-info">';
397 $this->HTML .= '<div class="panel-heading"><h3 class="panel-title">' . $Translation["saved filters title"] . "</h3></div>";
398 $this->HTML .= '<div class="panel-body">';
399 $this->HTML .= $Translation["saved filters instructions"];
400 $this->HTML .= '<textarea rows="4" class="form-control vspacer-lg" style="width: 100%;" onfocus="$j(this).select();">' . "<a href=\"{$filter_link}\">Saved filter link<a>" . '</textarea>';
401 $this->HTML .= "<div><a href=\"{$filter_link}\" title=\"" . html_attr($filter_link) . "\">{$Translation['permalink']}</a></div>";
402 $this->HTML .= '<button type="button" class="btn btn-default btn-block vspacer-lg" onclick="$j(\'#saved_filter_source_code\').remove();"><i class="glyphicon glyphicon-remove"></i> ' . $Translation['hide code'] . '</button>';
403 $this->HTML .= '</div>';
404 $this->HTML .= '</div>';
405 $this->HTML .= '</div></div>';
406 }
407
408 elseif($Filter_x != ''){
409 $orderBy = array();
410 if($SortField){
411 $sortFields = explode(',', $SortField);
412 $i=0;
413 foreach($sortFields as $sf){
414 $tob = preg_split('/\s+/', $sf, 2);
415 $orderBy[] = array(trim($tob[0]) => (strtolower(trim($tob[1]))=='desc' ? 'desc' : 'asc'));
416 $i++;
417 }
418 $orderBy[$i-1][$tob[0]] = (strtolower(trim($SortDirection))=='desc' ? 'desc' : 'asc');
419 }
420
421 $currDir=dirname(__FILE__).'/hooks'; // path to hooks folder
422 $uff="{$currDir}/{$this->TableName}.filters.{$mi['username']}.php"; // user-specific filter file
423 $gff="{$currDir}/{$this->TableName}.filters.{$mi['group']}.php"; // group-specific filter file
424 $tff="{$currDir}/{$this->TableName}.filters.php"; // table-specific filter file
425
426 /*
427 if no explicit filter file exists, look for filter files in the hooks folder in this order:
428 1. tablename.filters.username.php ($uff)
429 2. tablename.filters.groupname.php ($gff)
430 3. tablename.filters.php ($tff)
431 */
432 if(!is_file($this->FilterPage)){
433 $this->FilterPage='defaultFilters.php';
434 if(is_file($uff)){
435 $this->FilterPage=$uff;
436 }elseif(is_file($gff)){
437 $this->FilterPage=$gff;
438 }elseif(is_file($tff)){
439 $this->FilterPage=$tff;
440 }
441 }
442
443 if($this->FilterPage!=''){
444 ob_start();
445 @include($this->FilterPage);
446 $out=ob_get_contents();
447 ob_end_clean();
448 $this->HTML .= $out;
449 }
450 // hidden variables ....
451 $this->HTML .= '<input name="SortField" value="'.$SortField.'" type="hidden" />';
452 $this->HTML .= '<input name="SortDirection" type="hidden" value="'.$SortDirection.'" />';
453 $this->HTML .= '<input name="FirstRecord" type="hidden" value="1" />';
454
455 $this->ContentType='filters';
456 return;
457 }
458
459 elseif($NoFilter_x != ''){
460 // clear all filters ...
461 for($i = 1; $i <= (datalist_filters_count * $FiltersPerGroup); $i++){ // Number of filters allowed
462 $FilterField[$i] = '';
463 $FilterOperator[$i] = '';
464 $FilterValue[$i] = '';
465 }
466 $DisplayRecords = 'all';
467 $SearchString = '';
468 $FirstRecord = 1;
469
470 // clear filterers
471 foreach($this->filterers as $filterer => $caption){
472 $_REQUEST['filterer_' . $filterer] = '';
473 }
474 }
475
476 elseif($SelectedID){
477 $this->hideTV();
478 }
479
480 // apply lookup filterers to the query
481 foreach($this->filterers as $filterer => $caption){
482 if($_REQUEST['filterer_' . $filterer] != ''){
483 if($this->QueryWhere == '')
484 $this->QueryWhere = "where ";
485 else
486 $this->QueryWhere .= " and ";
487 $this->QueryWhere .= "`{$this->TableName}`.`$filterer`='" . makeSafe($_REQUEST['filterer_' . $filterer]) . "' ";
488 break; // currently, only one filterer can be applied at a time
489 }
490 }
491
492 // apply quick search to the query
493 if($SearchString != ''){
494 if($Search_x!=''){ $FirstRecord=1; }
495
496 if($this->QueryWhere=='')
497 $this->QueryWhere = "where ";
498 else
499 $this->QueryWhere .= " and ";
500
501 foreach($this->QueryFieldsQS as $fName => $fCaption){
502 if(strpos($fName, '<img')===False){
503 $this->QuerySearchableFields[$fName]=$fCaption;
504 }
505 }
506
507 $this->QueryWhere.='('.implode(" LIKE '%".makeSafe($SearchString)."%' or ", array_keys($this->QuerySearchableFields))." LIKE '%".makeSafe($SearchString)."%')";
508 }
509
510
511 // set query filters
512 $QueryHasWhere = 0;
513 if(strpos($this->QueryWhere, 'where ')!==FALSE)
514 $QueryHasWhere = 1;
515
516 $WhereNeedsClosing = 0;
517 for($i = 1; $i <= (datalist_filters_count * $FiltersPerGroup); $i+=$FiltersPerGroup){ // Number of filters allowed
518 // test current filter group
519 $GroupHasFilters = 0;
520 for($j = 0; $j < $FiltersPerGroup; $j++){
521 if($FilterField[$i+$j] != '' && $this->QueryFieldsIndexed[($FilterField[$i+$j])] != '' && $FilterOperator[$i+$j] != '' && ($FilterValue[$i+$j] != '' || strpos($FilterOperator[$i+$j], 'empty'))){
522 $GroupHasFilters = 1;
523 break;
524 }
525 }
526
527 if($GroupHasFilters){
528 if(!stristr($this->QueryWhere, "where "))
529 $this->QueryWhere = "where (";
530 elseif($QueryHasWhere){
531 $this->QueryWhere .= " and (";
532 $QueryHasWhere = 0;
533 }
534
535 $this->QueryWhere .= " <FilterGroup> " . $FilterAnd[$i] . " (";
536
537 for($j = 0; $j < $FiltersPerGroup; $j++){
538 if($FilterField[$i+$j] != '' && $this->QueryFieldsIndexed[($FilterField[$i+$j])] != '' && $FilterOperator[$i+$j] != '' && ($FilterValue[$i+$j] != '' || strpos($FilterOperator[$i+$j], 'empty'))){
539 if($FilterAnd[$i+$j]==''){
540 $FilterAnd[$i+$j]='and';
541 }
542 // test for date/time fields
543 $tries=0; $isDateTime=FALSE; $isDate=FALSE;
544 $fieldName=str_replace('`', '', $this->QueryFieldsIndexed[($FilterField[$i+$j])]);
545 list($tn, $fn)=explode('.', $fieldName);
546 while(!($res=sql("show columns from `$tn` like '$fn'", $eo)) && $tries<2){
547 $tn=substr($tn, 0, -1);
548 $tries++;
549 }
550 if($row = @db_fetch_array($res)){
551 if($row['Type']=='date' || $row['Type']=='time'){
552 $isDateTime=TRUE;
553 if($row['Type']=='date'){
554 $isDate=True;
555 }
556 }
557 }
558 // end of test
559 if($FilterOperator[$i+$j]=='is-empty' && !$isDateTime){
560 $this->QueryWhere .= " <FilterItem> " . $FilterAnd[$i+$j] . " (" . $this->QueryFieldsIndexed[($FilterField[$i+$j])] . "='' or " . $this->QueryFieldsIndexed[($FilterField[$i+$j])] . " is NULL) </FilterItem>";
561 }elseif($FilterOperator[$i+$j]=='is-not-empty' && !$isDateTime){
562 $this->QueryWhere .= " <FilterItem> " . $FilterAnd[$i+$j] . " " . $this->QueryFieldsIndexed[($FilterField[$i+$j])] . "!='' </FilterItem>";
563 }elseif($FilterOperator[$i+$j]=='is-empty' && $isDateTime){
564 $this->QueryWhere .= " <FilterItem> " . $FilterAnd[$i+$j] . " (" . $this->QueryFieldsIndexed[($FilterField[$i+$j])] . "=0 or " . $this->QueryFieldsIndexed[($FilterField[$i+$j])] . " is NULL) </FilterItem>";
565 }elseif($FilterOperator[$i+$j]=='is-not-empty' && $isDateTime){
566 $this->QueryWhere .= " <FilterItem> " . $FilterAnd[$i+$j] . " " . $this->QueryFieldsIndexed[($FilterField[$i+$j])] . "!=0 </FilterItem>";
567 }elseif($FilterOperator[$i+$j]=='like' && !strstr($FilterValue[$i+$j], "%") && !strstr($FilterValue[$i+$j], "_")){
568 $this->QueryWhere .= " <FilterItem> " . $FilterAnd[$i+$j] . " " . $this->QueryFieldsIndexed[($FilterField[$i+$j])] . " like '%" . makeSafe($FilterValue[$i+$j]) . "%' </FilterItem>";
569 }elseif($FilterOperator[$i+$j]=='not-like' && !strstr($FilterValue[$i+$j], "%") && !strstr($FilterValue[$i+$j], "_")){
570 $this->QueryWhere .= " <FilterItem> " . $FilterAnd[$i+$j] . " " . $this->QueryFieldsIndexed[($FilterField[$i+$j])] . " not like '%" . makeSafe($FilterValue[$i+$j]) . "%' </FilterItem>";
571 }elseif($isDate){
572 $dateValue = toMySQLDate($FilterValue[$i+$j]);
573 $this->QueryWhere .= " <FilterItem> " . $FilterAnd[$i+$j] . " " . $this->QueryFieldsIndexed[($FilterField[$i+$j])] . " " . $GLOBALS['filter_operators'][$FilterOperator[$i+$j]] . " '$dateValue' </FilterItem>";
574 }else{
575 $this->QueryWhere .= " <FilterItem> " . $FilterAnd[$i+$j] . " " . $this->QueryFieldsIndexed[($FilterField[$i+$j])] . " " . $GLOBALS['filter_operators'][$FilterOperator[$i+$j]] . " '" . makeSafe($FilterValue[$i+$j]) . "' </FilterItem>";
576 }
577 }
578 }
579
580 $this->QueryWhere .= ") </FilterGroup>";
581 $WhereNeedsClosing = 1;
582 }
583 }
584
585 if($WhereNeedsClosing)
586 $this->QueryWhere .= ")";
587
588 // set query sort
589 if(!stristr($this->QueryOrder, "order by ") && $SortField != '' && $this->AllowSorting){
590 $actualSortField = $SortField;
591 foreach($this->SortFields as $fieldNum => $fieldSort){
592 $actualSortField = str_replace(" $fieldNum ", " $fieldSort ", " $actualSortField ");
593 $actualSortField = str_replace(",$fieldNum ", ",$fieldSort ", " $actualSortField ");
594 }
595 $this->QueryOrder = "order by $actualSortField $SortDirection";
596 }
597
598 // clean up query
599 $this->QueryWhere = str_replace('( <FilterGroup> and ', '( ', $this->QueryWhere);
600 $this->QueryWhere = str_replace('( <FilterGroup> or ', '( ', $this->QueryWhere);
601 $this->QueryWhere = str_replace('( <FilterItem> and ', '( ', $this->QueryWhere);
602 $this->QueryWhere = str_replace('( <FilterItem> or ', '( ', $this->QueryWhere);
603 $this->QueryWhere = str_replace('<FilterGroup>', '', $this->QueryWhere);
604 $this->QueryWhere = str_replace('</FilterGroup>', '', $this->QueryWhere);
605 $this->QueryWhere = str_replace('<FilterItem>', '', $this->QueryWhere);
606 $this->QueryWhere = str_replace('</FilterItem>', '', $this->QueryWhere);
607
608 // if no 'order by' clause found, apply default sorting if specified
609 if($this->DefaultSortField != '' && $this->QueryOrder == ''){
610 $this->QueryOrder="order by ".$this->DefaultSortField." ".$this->DefaultSortDirection;
611 }
612
613 // get count of matching records ...
614 $TempQuery = 'SELECT count(1) from '.$this->QueryFrom.' '.$this->QueryWhere;
615 $RecordCount = sqlValue($TempQuery);
616 $FieldCountTV = count($this->QueryFieldsTV);
617 $FieldCountCSV = count($this->QueryFieldsCSV);
618 $FieldCountFilters = count($this->QueryFieldsFilters);
619 if(!$RecordCount){
620 $FirstRecord=1;
621 }
622
623 // Output CSV on request
624 if($CSV_x != ''){
625 $this->HTML = '';
626 if(datalist_db_encoding == 'UTF-8') $this->HTML = "\xEF\xBB\xBF"; // BOM characters for UTF-8 output
627
628 // execute query for CSV output
629 $fieldList='';
630 foreach($this->QueryFieldsCSV as $fn=>$fc)
631 $fieldList.="$fn as `$fc`, ";
632 $fieldList=substr($fieldList, 0, -2);
633 $csvQuery = 'SELECT '.$fieldList.' from '.$this->QueryFrom.' '.$this->QueryWhere.' '.$this->QueryOrder;
634
635 // hook: table_csv
636 if(function_exists($this->TableName.'_csv')){
637 $args = array();
638 $mq = call_user_func_array($this->TableName . '_csv', array($csvQuery, $mi, &$args));
639 $csvQuery = ($mq ? $mq : $csvQuery);
640 }
641
642 $result = sql($csvQuery, $eo);
643
644 // output CSV field names
645 for($i = 0; $i < $FieldCountCSV; $i++)
646 $this->HTML .= "\"" . db_field_name($result, $i) . "\"" . $this->CSVSeparator;
647 $this->HTML .= "\n\n";
648
649 // output CSV data
650 while($row = db_fetch_row($result)){
651 for($i = 0; $i < $FieldCountCSV; $i++)
652 $this->HTML .= "\"" . str_replace(array("\r\n", "\r", "\n", '"'), array(' ', ' ', ' ', '""'), strip_tags($row[$i])) . "\"" . $this->CSVSeparator;
653 $this->HTML .= "\n\n";
654 }
655 $this->HTML = str_replace($this->CSVSeparator . "\n\n", "\n", $this->HTML);
656 $this->HTML = substr($this->HTML, 0, - 1);
657
658 // clean any output buffers
659 while(@ob_end_clean());
660
661 // output CSV HTTP headers ...
662 header('HTTP/1.1 200 OK');
663 header('Date: ' . @date("D M j G:i:s T Y"));
664 header('Last-Modified: ' . @date("D M j G:i:s T Y"));
665 header("Content-Type: application/force-download");
666 header("Content-Length: " . (string)(strlen($this->HTML)));
667 header("Content-Transfer-Encoding: Binary");
668 header("Content-Disposition: attachment; filename=$this->TableName.csv");
669
670 // send output and quit script
671 echo $this->HTML;
672 exit;
673 }
674 $t = time(); // just a random number for any purpose ...
675
676 // should SelectedID be reset on clicking TV buttons?
677 $resetSelection = ($this->SeparateDV ? "document.myform.SelectedID.value = '';" : "document.myform.writeAttribute('novalidate', 'novalidate');");
678
679 if($current_view == 'DV' && !$Embedded){
680 $this->HTML .= '<div class="page-header">';
681 $this->HTML .= '<h1>';
682 $this->HTML .= '<a style="text-decoration: none; color: inherit;" href="' . $this->TableName . '_view.php"><img src="' . $this->TableIcon . '"> ' . $this->TableTitle . '</a>';
683 /* show add new button if user can insert and there is a selected record */
684 if($SelectedID && $this->Permissions[1] && $this->SeparateDV && $this->AllowInsert){
685 $this->HTML .= ' <button type="submit" id="addNew" name="addNew_x" value="1" class="btn btn-success"><i class="glyphicon glyphicon-plus-sign"></i> ' . $Translation['Add New'] . '</button>';
686 }
687 $this->HTML .= '</h1>';
688 $this->HTML .= '</div>';
689 }
690
691 // quick search and TV action buttons
692 if(!$this->HideTableView && !($dvprint_x && $this->AllowSelection && $SelectedID) && !$PrintDV){
693 $buttons_all = $quick_search_html = '';
694
695 if($Print_x == ''){
696
697 // display 'Add New' icon
698 if($this->Permissions[1] && $this->SeparateDV && $this->AllowInsert){
699 $buttons_all .= '<button type="submit" id="addNew" name="addNew_x" value="1" class="btn btn-success"><i class="glyphicon glyphicon-plus-sign"></i> ' . $Translation['Add New'] . '</button>';
700 $buttonsCount++;
701 }
702
703 // display Print icon
704 if($this->AllowPrinting){
705 $buttons_all .= '<button onClick="document.myform.NoDV.value=1; ' . $resetSelection . ' return true;" type="submit" name="Print_x" id="Print" value="1" class="btn btn-default"><i class="glyphicon glyphicon-print"></i> ' . $Translation['Print Preview'] . '</button>';
706 $buttonsCount++;
707 }
708
709 // display CSV icon
710 if($this->AllowCSV){
711 $buttons_all .= '<button onClick="document.myform.NoDV.value=1; ' . $resetSelection . ' return true;" type="submit" name="CSV_x" id="CSV" value="1" class="btn btn-default"><i class="glyphicon glyphicon-download-alt"></i> ' . $Translation['CSV'] . '</button>';
712 $buttonsCount++;
713 }
714
715 // display Filter icon
716 if($this->AllowFilters){
717 $buttons_all .= '<button onClick="document.myform.NoDV.value=1; ' . $resetSelection . ' return true;" type="submit" name="Filter_x" id="Filter" value="1" class="btn btn-default"><i class="glyphicon glyphicon-filter"></i> ' . $Translation['filter'] . '</button>';
718 $buttonsCount++;
719 }
720
721 // display Show All icon
722 if(($this->AllowFilters)){
723 $buttons_all .= '<button onClick="document.myform.NoDV.value=1; ' . $resetSelection . ' return true;" type="submit" name="NoFilter_x" id="NoFilter" value="1" class="btn btn-default"><i class="glyphicon glyphicon-remove-circle"></i> ' . $Translation['Reset Filters'] . '</button>';
724 $buttonsCount++;
725 }
726
727 $quick_search_html .= '<div class="input-group" id="quick-search">';
728 $quick_search_html .= '<input type="text" name="SearchString" value="' . html_attr($SearchString) . '" class="form-control" placeholder="' . html_attr($this->QuickSearchText) . '">';
729 $quick_search_html .= '<span class="input-group-btn">';
730 $quick_search_html .= '<button name="Search_x" value="1" id="Search" type="submit" onClick="' . $resetSelection . ' document.myform.NoDV.value=1; return true;" class="btn btn-default" title="' . html_attr($this->QuickSearchText) . '"><i class="glyphicon glyphicon-search"></i></button>';
731 $quick_search_html .= '<button name="NoFilter_x" value="1" id="NoFilter_x" type="submit" onClick="' . $resetSelection . ' document.myform.NoDV.value=1; return true;" class="btn btn-default" title="' . html_attr($Translation['Reset Filters']) . '"><i class="glyphicon glyphicon-remove-circle"></i></button>';
732 $quick_search_html .= '</span>';
733 $quick_search_html .= '</div>';
734 }else{
735 $buttons_all .= '<button class="btn btn-primary" type="button" id="sendToPrinter" onClick="window.print();"><i class="glyphicon glyphicon-print"></i> ' . $Translation['Print'] . '</button>';
736 $buttons_all .= '<button class="btn btn-default" type="submit"><i class="glyphicon glyphicon-remove-circle"></i> ' . $Translation['Cancel Printing'] . '</button>';
737 }
738
739 /* if user can print DV, add action to 'More' menu */
740 $selected_records_more = array();
741
742 if($AllowPrintDV){
743 $selected_records_more[] = array(
744 'function' => ($this->SeparateDV ? 'print_multiple_dv_sdv' : 'print_multiple_dv_tvdv'),
745 'title' => $Translation['Print Preview Detail View'],
746 'icon' => 'print'
747 );
748 }
749
750 /* if user can mass-delete selected records, add action to 'More' menu */
751 if($this->AllowMassDelete && $this->AllowDelete){
752 $selected_records_more[] = array(
753 'function' => 'mass_delete',
754 'title' => $Translation['Delete'],
755 'icon' => 'trash',
756 'class' => 'text-danger'
757 );
758 }
759
760 /* if user is admin, add 'Change owner' action to 'More' menu */
761 /* also, add help link for adding more actions */
762 if($mi['admin']){
763 $selected_records_more[] = array(
764 'function' => 'mass_change_owner',
765 'title' => $Translation['Change owner'],
766 'icon' => 'user'
767 );
768 $selected_records_more[] = array(
769 'function' => 'add_more_actions_link',
770 'title' => $Translation['Add more actions'],
771 'icon' => 'question-sign',
772 'class' => 'text-info'
773 );
774 }
775
776 /* user-defined actions ... should be set in the {tablename}_batch_actions() function in hooks/{tablename}.php */
777 $user_actions = array();
778 if(function_exists($this->TableName.'_batch_actions')){
779 $args = array();
780 $user_actions = call_user_func_array($this->TableName . '_batch_actions', array(&$args));
781 if(is_array($user_actions) && count($user_actions)){
782 $selected_records_more = array_merge($selected_records_more, $user_actions);
783 }
784 }
785
786 $actual_more_count = 0;
787 $more_menu = $more_menu_js = '';
788 if(count($selected_records_more)){
789 $more_menu .= '<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" id="selected_records_more"><i class="glyphicon glyphicon-check"></i> ' . $Translation['More'] . ' <span class="caret"></span></button>';
790 $more_menu .= '<ul class="dropdown-menu" role="menu">';
791 foreach($selected_records_more as $action){
792 if(!$action['function'] || !$action['title']) continue;
793 $action['class'] = (!isset($action['class']) ? '' : $action['class']);
794 $action['icon'] = (!isset($action['icon']) ? '' : $action['icon']);
795 $actual_more_count++;
796 $more_menu .= '<li>' .
797 '<a href="#" id="selected_records_' . $action['function'] . '">' .
798 '<span class="' . $action['class'] . '">' .
799 ($action['icon'] ? '<i class="glyphicon glyphicon-' . $action['icon'] . '"></i> ' : '') .
800 $action['title'] .
801 '</span>' .
802 '</a>' .
803 '</li>';
804
805 // on clicking an action, call its js handler function, passing the current table name and an array of selected IDs to it
806 $more_menu_js .= "jQuery('[id=selected_records_{$action['function']}]').click(function(){ {$action['function']}('{$this->TableName}', get_selected_records_ids()); return false; });";
807 }
808 $more_menu .= '</ul>';
809 }
810
811 if($Embedded){
812 $this->HTML .= '<script>$j(function(){ $j(\'[id^=notification-]\').parent().css({\'margin-top\': \'15px\', \'margin-bottom\': \'0\'}); })</script>';
813 }else{
814 $this->HTML .= '<div class="page-header">';
815 $this->HTML .= '<h1>';
816 $this->HTML .= '<div class="row">';
817 $this->HTML .= '<div class="col-sm-8">';
818 $this->HTML .= '<a style="text-decoration: none; color: inherit;" href="' . $this->TableName . '_view.php"><img src="' . $this->TableIcon . '"> ' . $this->TableTitle . '</a>';
819 $this->HTML .= '</div>';
820 if($this->QuickSearch){
821 $this->HTML .= '<div class="col-sm-4">';
822 $this->HTML .= $quick_search_html;
823 $this->HTML .= '</div>';
824 }
825 $this->HTML .= '</div>';
826 $this->HTML .= '</h1>';
827 $this->HTML .= '</div>';
828
829 $this->HTML .= '<div id="top_buttons" class="hidden-print">';
830 /* .all_records: container for buttons that don't need a selection */
831 /* .selected_records: container for buttons that need a selection */
832 $this->HTML .= '<div class="btn-group btn-group-lg visible-md visible-lg all_records pull-left">' . $buttons_all . '</div>';
833 $this->HTML .= '<div class="btn-group btn-group-lg visible-md visible-lg selected_records hidden pull-left hspacer-lg">' . $buttons_selected . ($actual_more_count ? $more_menu : '') . '</div>';
834 $this->HTML .= '<div class="btn-group-vertical btn-group-lg visible-xs visible-sm all_records">' . $buttons_all . '</div>';
835 $this->HTML .= '<div class="btn-group-vertical btn-group-lg visible-xs visible-sm selected_records hidden vspacer-lg">' . $buttons_selected . ($actual_more_count ? $more_menu : '') . '</div>';
836 $this->HTML .= '<div class="clearfix"></div><p></p>';
837 $this->HTML .= '</div>';
838
839 $this->HTML .= '<div class="row"><div class="table_view col-xs-12 ' . $this->TVClasses . '">';
840 }
841
842 if($Print_x != ''){
843 /* fix top margin for print-preview */
844 $this->HTML .= '<style>body{ padding-top: 0 !important; }</style>';
845
846 /* disable links inside table body to prevent printing their href */
847 $this->HTML .= '<script>jQuery(function(){ jQuery("tbody a").removeAttr("href").removeAttr("rel"); });</script>';
848 }
849
850 // script for focusing into the search box on loading the page
851 // and for declaring record action handlers
852 $this->HTML .= '<script>jQuery(function(){ jQuery("input[name=SearchString]").focus(); ' . $more_menu_js . ' });</script>';
853
854 }
855
856 // begin table and display table title
857 if(!$this->HideTableView && !($dvprint_x && $this->AllowSelection && $SelectedID) && !$PrintDV && !$Embedded){
858 $this->HTML .= '<div class="table-responsive"><table class="table table-striped table-bordered table-hover">';
859
860 $this->HTML .= '<thead><tr>';
861 if(!$Print_x) $this->HTML .= '<th style="width: 18px;" class="text-center"><input class="hidden-print" type="checkbox" title="' . html_attr($Translation['Select all records']) . '" id="select_all_records"></th>';
862 // Templates
863 $rowTemplate = $selrowTemplate = '';
864 if($this->Template){
865 $rowTemplate = @file_get_contents('./' . $this->Template);
866 if($rowTemplate && $this->SelectedTemplate){
867 $selrowTemplate = @file_get_contents('./' . $this->SelectedTemplate);
868 }
869 }
870
871 // process translations
872 if($rowTemplate){
873 foreach($Translation as $symbol=>$trans){
874 $rowTemplate=str_replace("<%%TRANSLATION($symbol)%%>", $trans, $rowTemplate);
875 }
876 }
877 if($selrowTemplate){
878 foreach($Translation as $symbol=>$trans){
879 $selrowTemplate=str_replace("<%%TRANSLATION($symbol)%%>", $trans, $selrowTemplate);
880 }
881 }
882 // End of templates
883
884 // $this->ccffv: map $FilterField values to field captions as stored in ColCaption
885 $this->ccffv = array();
886 foreach($this->ColCaption as $captionIndex => $caption){
887 $ffv = 1;
888 foreach($this->QueryFieldsFilters as $uselessKey => $filterCaption){
889 if($caption == $filterCaption){
890 $this->ccffv[$captionIndex] = $ffv;
891 }
892 $ffv++;
893 }
894 }
895
896 // display table headers
897 $forceHeaderWidth = false;
898 if($rowTemplate=='' || $this->ShowTableHeader){
899 for($i = 0; $i < count($this->ColCaption); $i++){
900 /* Sorting icon and link */
901 $sort1 = $sort2 = $filterHint = '';
902 if($this->AllowSorting == 1){
903 if($current_view != 'TVP'){
904 $sort1 = "<a href=\"{$this->ScriptFileName}?SortDirection=asc&SortField=".($this->ColNumber[$i])."\" onClick=\"$resetSelection document.myform.NoDV.value=1; document.myform.SortDirection.value='asc'; document.myform.SortField.value = '".($this->ColNumber[$i])."'; document.myform.submit(); return false;\" class=\"TableHeader\">";
905 $sort2 = "</a>";
906 }
907 if($this->ColNumber[$i] == $SortField){
908 $SortDirection = ($SortDirection == "asc" ? "desc" : "asc");
909 if($current_view != 'TVP')
910 $sort1 = "<a href=\"{$this->ScriptFileName}?SortDirection=$SortDirection&SortField=".($this->ColNumber[$i])."\" onClick=\"$resetSelection document.myform.NoDV.value=1; document.myform.SortDirection.value='$SortDirection'; document.myform.SortField.value = ".($this->ColNumber[$i])."; document.myform.submit(); return false;\" class=\"TableHeader\">";
911 $sort2 = " <i class=\"text-warning glyphicon glyphicon-sort-by-attributes" . ($SortDirection == 'desc' ? '' : '-alt') . "\"></i>{$sort2}";
912 $SortDirection = ($SortDirection == "asc" ? "desc" : "asc");
913 }
914 }
915
916 /* Filtering icon and hint */
917 if($this->AllowFilters && is_array($FilterField)){
918 // check to see if there is any filter applied on the current field
919 if(isset($this->ccffv[$i]) && in_array($this->ccffv[$i], $FilterField)){
920 // render filter icon
921 $filterHint = ' <button type="submit" class="btn btn-default btn-xs' . ($current_view == 'TVP' ? ' disabled' : '') . '" name="Filter_x" value="1" title="'.html_attr($Translation['filtered field']).'"><i class="glyphicon glyphicon-filter"></i></button>';
922 }
923 }
924
925 $this->HTML .= "\t<th class=\"{$this->TableName}-{$this->ColFieldName[$i]}\" " . ($forceHeaderWidth ? ' style="width: ' . ($this->ColWidth[$i] ? $this->ColWidth[$i] : 100) . 'px;"' : '') . ">{$sort1}{$this->ColCaption[$i]}{$sort2}{$filterHint}</th>\n";
926 }
927 }elseif($current_view != 'TVP'){
928 // Display a Sort by drop down
929 $this->HTML .= "\t<th class=\"hidden-print\" colspan=\"" . (count($this->ColCaption)) . "\">";
930 $this->HTML .= "\t<div class=\"pull-right\" id=\"order-by-selector\">";
931
932 if($this->AllowSorting == 1){
933 $sortCombo = new Combo;
934 for($i=0; $i < count($this->ColCaption); $i++){
935 $sortCombo->ListItem[] = $this->ColCaption[$i];
936 $sortCombo->ListData[] = $this->ColNumber[$i];
937 }
938 $sortCombo->SelectName = "FieldsList";
939 $sortCombo->SelectedData = $SortField;
940 $sortCombo->Class = '';
941 $sortCombo->SelectedClass = '';
942 $sortCombo->Render();
943 $sortby_dropdown = $sortCombo->HTML;
944 $sortby_dropdown = str_replace('<select ', "<select onChange=\"document.myform.SortDirection.value='$SortDirection'; document.myform.SortField.value=document.myform.FieldsList.value; document.myform.NoDV.value=1; document.myform.submit();\" ", $sortby_dropdown);
945 if($SortField){
946 $SortDirection = ($SortDirection == 'desc' ? 'asc' : 'desc');
947 $sort_class = ($SortDirection == 'asc' ? 'sort-by-attributes-alt' : 'sort-by-attributes');
948 $sort = "<a href=\"javascript: document.myform.NoDV.value=1; document.myform.SortDirection.value='{$SortDirection}'; document.myform.SortField.value='{$SortField}'; document.myform.submit();\" class=TableHeader><i class=\"text-warning glyphicon glyphicon-{$sort_class}\"></i></a>";
949 $SortDirection = ($SortDirection == 'desc' ? 'asc' : 'desc');
950 }else{
951 $sort = '';
952 }
953
954 $sortby_sep = '<span class="hspacer-md"></span>';
955
956 $this->HTML .= "{$Translation['order by']}{$sortby_sep}{$sortby_dropdown}{$sortby_sep}{$sort}{$sortby_sep}";
957 }
958 $this->HTML .= "</div><style>#s2id_FieldsList{ min-width: 12em; width: unset !important; }</style></th>\n";
959 }
960
961 // table view navigation code ...
962 if($RecordCount && $this->AllowNavigation && $RecordCount>$this->RecordsPerPage){
963 while($FirstRecord > $RecordCount)
964 $FirstRecord -= $this->RecordsPerPage;
965
966 if($FirstRecord == '' || $FirstRecord < 1) $FirstRecord = 1;
967
968 if($Previous_x != ''){
969 $FirstRecord -= $this->RecordsPerPage;
970 if($FirstRecord <= 0)
971 $FirstRecord = 1;
972 }elseif($Next_x != ''){
973 $FirstRecord += $this->RecordsPerPage;
974 if($FirstRecord > $RecordCount)
975 $FirstRecord = $RecordCount - ($RecordCount % $this->RecordsPerPage) + 1;
976 if($FirstRecord > $RecordCount)
977 $FirstRecord = $RecordCount - $this->RecordsPerPage + 1;
978 if($FirstRecord <= 0)
979 $FirstRecord = 1;
980 }
981
982 }elseif($RecordCount){
983 $FirstRecord = 1;
984 $this->RecordsPerPage = 2000; // a limit on max records in print preview to avoid performance drops
985 }
986 // end of table view navigation code
987 $this->HTML .= "\n\t</tr>\n\n</thead>\n\n<tbody><!-- tv data below -->\n";
988
989 $i = 0;
990 $hc=new CI_Input();
991 $hc->charset = datalist_db_encoding;
992 if($RecordCount){
993 $i = $FirstRecord;
994 // execute query for table view
995 $query_fields = array();
996 foreach($this->QueryFieldsTV as $fn => $fc)
997 $query_fields[] = "$fn as `$fc`";
998 $fieldList = implode(', ', $query_fields);
999
1000 if($this->PrimaryKey)
1001 $fieldList .= ", $this->PrimaryKey as '" . str_replace('`', '', $this->PrimaryKey) . "'";
1002
1003 $tvQuery = "SELECT {$fieldList} from {$this->QueryFrom} {$this->QueryWhere} {$this->QueryOrder}";
1004 $result = sql($tvQuery . " limit " . ($i-1) . ",{$this->RecordsPerPage}", $eo);
1005 while(($row = db_fetch_array($result)) && ($i < ($FirstRecord + $this->RecordsPerPage))){
1006 /* skip displaying the current record if we're in TVP or multiple DVP and the record is not checked */
1007 if(($PrintTV || $Print_x) && count($_REQUEST['record_selector']) && !in_array($row[$FieldCountTV], $_REQUEST['record_selector'])) continue;
1008
1009 $attr_id = html_attr($row[$FieldCountTV]); /* pk value suitable for inserting into html tag attributes */
1010 $js_id = addslashes($row[$FieldCountTV]); /* pk value suitable for inserting into js strings */
1011
1012 /* show record selector except in TVP */
1013 if($Print_x != ''){ $this->HTML .= '<tr>'; }
1014
1015 if(!$Print_x){
1016 $this->HTML .= ($SelectedID == $row[$FieldCountTV] ? '<tr class="active">' : '<tr>');
1017 $checked = (is_array($_REQUEST['record_selector']) && in_array($row[$FieldCountTV], $_REQUEST['record_selector']) ? ' checked' : '');
1018 $this->HTML .= "<td class=\"text-center\"><input class=\"hidden-print record_selector\" type=\"checkbox\" id=\"record_selector_{$attr_id}\" name=\"record_selector[]\" value=\"{$attr_id}\"{$checked}></td>";
1019 }
1020
1021 /* apply record templates */
1022 if($rowTemplate != ''){
1023 $rowTemp = $rowTemplate;
1024 if($this->AllowSelection == 1 && $SelectedID == $row[$FieldCountTV] && $selrowTemplate != ''){
1025 $rowTemp = $selrowTemplate;
1026 }
1027
1028 if($this->AllowSelection == 1 && $SelectedID != $row[$FieldCountTV]){
1029 $rowTemp = str_replace('<%%SELECT%%>',"<a onclick=\"document.myform.SelectedField.value=this.parentNode.cellIndex; document.myform.SelectedID.value='" . addslashes($row[$FieldCountTV]) . "'; document.myform.submit(); return false;\" href=\"{$this->ScriptFileName}?SelectedID=" . html_attr($row[$FieldCountTV]) . "\" style=\"display: block; padding:0px;\">",$rowTemp);
1030 $rowTemp = str_replace('<%%ENDSELECT%%>','</a>',$rowTemp);
1031 }else{
1032 $rowTemp = str_replace('<%%SELECT%%>', '', $rowTemp);
1033 $rowTemp = str_replace('<%%ENDSELECT%%>', '', $rowTemp);
1034 }
1035
1036 for($j = 0; $j < $FieldCountTV; $j++){
1037 $fieldTVCaption = current(array_slice($this->QueryFieldsTV, $j, 1));
1038
1039 $fd = $row[$j];
1040 /* apply nl2br only for non-HTML data */
1041 if($row[$j] == strip_tags($row[$j])){
1042 $fd = nl2br($row[$j]);
1043 }
1044
1045 /* Sanitize output against XSS attacks */
1046 $fd = $hc->xss_clean($fd);
1047
1048 /*
1049 the TV template could contain field placeholders in the format
1050 <%%FIELD_n%%> or <%%VALUE(Field caption)%%> or <%%HTML_ATTR(field caption)%%>
1051 */
1052 $rowTemp = str_replace("<%%FIELD_$j%%>", thisOr($fd, ''), $rowTemp);
1053 $rowTemp = str_replace("<%%VALUE($fieldTVCaption)%%>", thisOr($fd, ''), $rowTemp);
1054 $rowTemp = str_replace("<%%HTML_ATTR($fieldTVCaption)%%>", html_attr($fd), $rowTemp);
1055
1056 if(strpos($rowTemp, "<%%YOUTUBETHUMB($fieldTVCaption)%%>") !== false) $rowTemp = str_replace("<%%YOUTUBETHUMB($fieldTVCaption)%%>", thisOr(get_embed('youtube', $fd, '', '', 'thumbnail_url'), 'blank.gif'), $rowTemp);
1057 if(strpos($rowTemp, "<%%GOOGLEMAPTHUMB($fieldTVCaption)%%>") !== false) $rowTemp = str_replace("<%%GOOGLEMAPTHUMB($fieldTVCaption)%%>", thisOr(get_embed('googlemap', $fd, '', '', 'thumbnail_url'), 'blank.gif'), $rowTemp);
1058 if(thisOr($fd)==' ' && preg_match('/<a href=".*? .*?<\/a>/i', $rowTemp, $m)){
1059 $rowTemp=str_replace($m[0], '', $rowTemp);
1060 }
1061 }
1062
1063 $this->HTML .= $rowTemp;
1064 $rowTemp = '';
1065
1066 }else{
1067 // default view if no template
1068 for($j = 0; $j < $FieldCountTV; $j++){
1069 if($this->AllowSelection == 1){
1070 $sel1 = "<a href=\"{$this->ScriptFileName}?SelectedID=" . html_attr($row[$FieldCountTV]) . "\" onclick=\"document.myform.SelectedID.value='" . addslashes($row[$FieldCountTV]) . "'; document.myform.submit(); return false;\" style=\"padding:0px;\">";
1071 $sel2 = "</a>";
1072 }else{
1073 $sel1 = '';
1074 $sel2 = '';
1075 }
1076
1077 $this->HTML .= "<td valign=\"top\"><div> {$sel1}{$row[$j]}{$sel2} </div></td>";
1078 }
1079 }
1080 $this->HTML .= "</tr>\n";
1081 $i++;
1082 }
1083 $i--;
1084 }
1085
1086 $this->HTML = preg_replace("/<a href=\"(mailto:)? [^\n]*title=\" \"><\/a>/", ' ', $this->HTML);
1087 $this->HTML = preg_replace("/<a [^>]*>( )*<\/a>/", ' ', $this->HTML);
1088 $this->HTML = preg_replace("/<%%.*%%>/U", ' ', $this->HTML);
1089 // end of data
1090 $this->HTML.='<!-- tv data above -->';
1091 $this->HTML .= "\n</tbody>";
1092
1093 if($Print_x == ''){ // TV
1094 $pagesMenu = '';
1095 if($RecordCount > $this->RecordsPerPage){
1096 $pagesMenuId = "{$this->TableName}_pagesMenu";
1097 $pagesMenu = $Translation['go to page'] . ' <select class="input-sm" id="' . $pagesMenuId . '" onChange="document.myform.writeAttribute(\'novalidate\', \'novalidate\'); document.myform.NoDV.value=1; document.myform.FirstRecord.value=(this.value * ' . $this->RecordsPerPage . '+1); document.myform.submit();">';
1098 $pagesMenu .= '</select>';
1099
1100 $pagesMenu .= '<script>';
1101 $pagesMenu .= 'var lastPage = ' . (ceil($RecordCount / $this->RecordsPerPage) - 1) . ';';
1102 $pagesMenu .= 'var currentPage = ' . (($FirstRecord - 1) / $this->RecordsPerPage) . ';';
1103 $pagesMenu .= 'var pagesMenu = document.getElementById("' . $pagesMenuId . '");';
1104 $pagesMenu .= 'var lump = ' . datalist_max_page_lump . ';';
1105
1106 $pagesMenu .= 'if(lastPage <= lump * 3){';
1107 $pagesMenu .= ' addPageNumbers(0, lastPage);';
1108 $pagesMenu .= '}else{';
1109 $pagesMenu .= ' addPageNumbers(0, lump - 1);';
1110 $pagesMenu .= ' if(currentPage < lump) addPageNumbers(lump, currentPage + lump / 2);';
1111 $pagesMenu .= ' if(currentPage >= lump && currentPage < (lastPage - lump)){';
1112 $pagesMenu .= ' addPageNumbers(';
1113 $pagesMenu .= ' Math.max(currentPage - lump / 2, lump),';
1114 $pagesMenu .= ' Math.min(currentPage + lump / 2, lastPage - lump - 1)';
1115 $pagesMenu .= ' );';
1116 $pagesMenu .= ' }';
1117 $pagesMenu .= ' if(currentPage >= (lastPage - lump)) addPageNumbers(currentPage - lump / 2, lastPage - lump - 1);';
1118 $pagesMenu .= ' addPageNumbers(lastPage - lump, lastPage);';
1119 $pagesMenu .= '}';
1120
1121 $pagesMenu .= 'function addPageNumbers(fromPage, toPage){';
1122 $pagesMenu .= ' var ellipsesIndex = 0;';
1123 $pagesMenu .= ' if(fromPage > toPage) return;';
1124 $pagesMenu .= ' if(fromPage > 0){';
1125 $pagesMenu .= ' if(pagesMenu.options[pagesMenu.options.length - 1].text != fromPage){';
1126 $pagesMenu .= ' ellipsesIndex = pagesMenu.options.length;';
1127 $pagesMenu .= ' fromPage--;';
1128 $pagesMenu .= ' }';
1129 $pagesMenu .= ' }';
1130 $pagesMenu .= ' for(i = fromPage; i <= toPage; i++){';
1131 $pagesMenu .= ' var option = document.createElement("option");';
1132 $pagesMenu .= ' option.text = (i + 1);';
1133 $pagesMenu .= ' option.value = i;';
1134 $pagesMenu .= ' if(i == currentPage){ option.selected = "selected"; }';
1135 $pagesMenu .= ' try{';
1136 $pagesMenu .= ' /* for IE earlier than version 8 */';
1137 $pagesMenu .= ' pagesMenu.add(option, pagesMenu.options[null]);';
1138 $pagesMenu .= ' }catch(e){';
1139 $pagesMenu .= ' pagesMenu.add(option, null);';
1140 $pagesMenu .= ' }';
1141 $pagesMenu .= ' }';
1142 $pagesMenu .= ' if(ellipsesIndex > 0){';
1143 $pagesMenu .= ' pagesMenu.options[ellipsesIndex].text = " ... ";';
1144 $pagesMenu .= ' }';
1145 $pagesMenu .= '}';
1146 $pagesMenu .= '</script>';
1147 }
1148
1149 $this->HTML .= "\n\t";
1150
1151 if($i){ // 1 or more records found
1152 $this->HTML .= "<tfoot><tr><td colspan=".(count($this->ColCaption)+1).'>';
1153 $this->HTML .= $Translation['records x to y of z'];
1154 $this->HTML .= '</td></tr></tfoot>';
1155 }
1156
1157 if(!$i){ // no records found
1158 $this->HTML .= "<tfoot><tr><td colspan=".(count($this->ColCaption)+1).'>';
1159 $this->HTML .= '<div class="alert alert-warning">';
1160 $this->HTML .= '<i class="glyphicon glyphicon-warning-sign"></i> ';
1161 $this->HTML .= $Translation['No matches found!'];
1162 $this->HTML .= '</div>';
1163 $this->HTML .= '</td></tr></tfoot>';
1164 }
1165
1166 }else{ // TVP
1167 if($i) $this->HTML .= "\n\t<tfoot><tr><td colspan=".(count($this->ColCaption) + 1). '>' . $Translation['records x to y of z'] . '</td></tr></tfoot>';
1168 if(!$i) $this->HTML .= "\n\t<tfoot><tr><td colspan=".(count($this->ColCaption) + 1). '>' . $Translation['No matches found!'] . '</td></tr></tfoot>';
1169 }
1170
1171 $this->HTML = str_replace("<FirstRecord>", number_format($FirstRecord), $this->HTML);
1172 $this->HTML = str_replace("<LastRecord>", number_format($i), $this->HTML);
1173 $this->HTML = str_replace("<RecordCount>", number_format($RecordCount), $this->HTML);
1174 $tvShown=true;
1175
1176 $this->HTML .= "</table></div>\n";
1177
1178 /* highlight quick search matches */
1179 if($SearchString!='') $this->HTML .= '<script>$j(function(){ $j(".table-responsive td").mark("' . html_attr($SearchString) . '", { className: "text-warning bg-warning", diacritics: false }); })</script>';
1180
1181 if($Print_x == '' && $i){ // TV
1182 $this->HTML .= '<div class="row pagination-section">';
1183 $this->HTML .= '<div class="col-sm-4 col-md-3 col-lg-2 vspacer-lg">';
1184 $this->HTML .= '<button onClick="' . $resetSelection . ' document.myform.NoDV.value = 1; return true;" type="submit" name="Previous_x" id="Previous" value="1" class="btn btn-default btn-block"><i class="glyphicon glyphicon-chevron-left"></i> ' . $Translation['Previous'] . '</button>';
1185 $this->HTML .= '</div>';
1186
1187 $this->HTML .= '<div class="col-sm-4 col-md-4 col-lg-2 col-md-offset-1 col-lg-offset-3 text-center vspacer-lg">';
1188 $this->HTML .= $pagesMenu;
1189 $this->HTML .= '</div>';
1190
1191 $this->HTML .= '<div class="col-sm-4 col-md-3 col-lg-2 col-md-offset-1 col-lg-offset-3 text-right vspacer-lg">';
1192 $this->HTML .= '<button onClick="'.$resetSelection.' document.myform.NoDV.value=1; return true;" type="submit" name="Next_x" id="Next" value="1" class="btn btn-default btn-block">' . $Translation['Next'] . ' <i class="glyphicon glyphicon-chevron-right"></i></button>';
1193 $this->HTML .= '</div>';
1194 $this->HTML .= '</div>';
1195 }
1196
1197 $this->HTML .= '</div>'; // end of div.table_view
1198 }
1199 /* that marks the end of the TV table */
1200
1201 // hidden variables ....
1202 foreach($this->filterers as $filterer => $caption){
1203 if($_REQUEST['filterer_' . $filterer] != ''){
1204 $this->HTML .= "<input name=\"filterer_{$filterer}\" value=\"" . html_attr($_REQUEST['filterer_' . $filterer]) . "\" type=\"hidden\" />";
1205 break; // currently, only one filterer can be applied at a time
1206 }
1207 }
1208
1209 $this->HTML .= '<!-- possible values for current_view: TV, TVP, DV, DVP, Filters, TVDV -->';
1210 $this->HTML .= '<input name="current_view" id="current_view" value="' . $current_view . '" type="hidden">';
1211 $this->HTML .= '<input name="SortField" value="' . $SortField . '" type="hidden">';
1212 $this->HTML .= '<input name="SelectedID" value="' . html_attr($SelectedID) . '" type="hidden">';
1213 $this->HTML .= '<input name="SelectedField" value="" type="hidden">';
1214 $this->HTML .= '<input name="SortDirection" type="hidden" value="' . $SortDirection . '">';
1215 $this->HTML .= '<input name="FirstRecord" type="hidden" value="' . $FirstRecord . '">';
1216 $this->HTML .= '<input name="NoDV" type="hidden" value="">';
1217 $this->HTML .= '<input name="PrintDV" type="hidden" value="">';
1218 if($this->QuickSearch && !strpos($this->HTML, 'SearchString')) $this->HTML .= '<input name="SearchString" type="hidden" value="' . html_attr($SearchString) . '">';
1219 // hidden variables: filters ...
1220 $FiltersCode = '';
1221 for($i = 1; $i <= (datalist_filters_count * $FiltersPerGroup); $i++){ // Number of filters allowed
1222 if($i%$FiltersPerGroup == 1 && $i != 1 && $FilterAnd[$i] != ''){
1223 $FiltersCode .= "<input name=\"FilterAnd[$i]\" value=\"$FilterAnd[$i]\" type=\"hidden\">\n";
1224 }
1225 if($FilterField[$i] != '' && $FilterOperator[$i] != '' && ($FilterValue[$i] != '' || strpos($FilterOperator[$i], 'empty'))){
1226 if(!strstr($FiltersCode, "<input name=\"FilterAnd[{$i}]\" value="))
1227 $FiltersCode .= "<input name=\"FilterAnd[{$i}]\" value=\"{$FilterAnd[$i]}\" type=\"hidden\">\n";
1228 $FiltersCode .= "<input name=\"FilterField[{$i}]\" value=\"{$FilterField[$i]}\" type=\"hidden\">\n";
1229 $FiltersCode .= "<input name=\"FilterOperator[{$i}]\" value=\"{$FilterOperator[$i]}\" type=\"hidden\">\n";
1230 $FiltersCode .= "<input name=\"FilterValue[{$i}]\" value=\"" . html_attr($FilterValue[$i]) . "\" type=\"hidden\">\n";
1231 }
1232 }
1233 $FiltersCode .= "<input name=\"DisplayRecords\" value=\"$DisplayRecords\" type=\"hidden\" />";
1234 $this->HTML .= $FiltersCode;
1235
1236 // display details form ...
1237 if(($this->AllowSelection || $this->AllowInsert || $this->AllowUpdate || $this->AllowDelete) && $Print_x=='' && !$PrintDV){
1238 if(($this->SeparateDV && $this->HideTableView) || !$this->SeparateDV){
1239 $dvCode = call_user_func("{$this->TableName}_form", $SelectedID, $this->AllowUpdate, (($this->HideTableView && $SelectedID) ? 0 : $this->AllowInsert), $this->AllowDelete, $this->SeparateDV, $this->TemplateDV, $this->TemplateDVP);
1240
1241 $this->HTML .= "\n\t<div class=\"col-xs-12 detail_view {$this->DVClasses}\">{$tv_dv_separator}<div class=\"panel panel-default\">{$dvCode}</div></div>";
1242 $this->HTML .= ($this->SeparateDV ? '<input name="SearchString" value="' . html_attr($SearchString) . '" type="hidden">' : '');
1243 if($dvCode){
1244 $this->ContentType = 'detailview';
1245 $dvShown = true;
1246 }
1247 }
1248 }
1249
1250 // display multiple printable detail views
1251 if($PrintDV){
1252 $dvCode = '';
1253 $_REQUEST['dvprint_x'] = 1;
1254
1255 // hidden vars
1256 foreach($this->filterers as $filterer => $caption){
1257 if($_REQUEST['filterer_' . $filterer] != ''){
1258 $this->HTML .= "<input name=\"filterer_{$filterer}\" value=\"" . html_attr($_REQUEST['filterer_' . $filterer]) . "\" type=\"hidden\" />";
1259 break; // currently, only one filterer can be applied at a time
1260 }
1261 }
1262
1263 // count selected records
1264 $selectedRecords = 0;
1265 if(is_array($_REQUEST['record_selector'])) foreach($_REQUEST['record_selector'] as $id){
1266 $selectedRecords++;
1267 $this->HTML .= '<input type="hidden" name="record_selector[]" value="' . html_attr($id) . '">'."\n";
1268 }
1269
1270 if($selectedRecords && $selectedRecords <= datalist_max_records_dv_print){ // if records selected > {datalist_max_records_dv_print} don't show DV preview to avoid db performance issues.
1271 foreach($_REQUEST['record_selector'] as $id){
1272 $dvCode .= call_user_func($this->TableName . '_form', $id, 0, 0, 0, 1, $this->TemplateDV, $this->TemplateDVP);
1273 }
1274
1275 if($dvCode!=''){
1276 $dvCode = preg_replace('/<input .*?type="?image"?.*?>/', '', $dvCode);
1277 $this->HTML .= $dvCode;
1278 }
1279 }else{
1280 $this->HTML .= error_message($Translation['Maximum records allowed to enable this feature is'] . ' ' . datalist_max_records_dv_print);
1281 $this->HTML .= '<input type="submit" class="print-button" value="'.$Translation['Print Preview Table View'].'">';
1282 }
1283 }
1284
1285 $this->HTML .= "</div></form>";
1286 $this->HTML .= '</div><div class="col-xs-1 md-hidden lg-hidden"></div></div>';
1287
1288 // $this->HTML .= '<font face="garamond">'.html_attr($tvQuery).'</font>'; // uncomment this line for debugging the table view query
1289
1290 if($dvShown && $tvShown) $this->ContentType='tableview+detailview';
1291 if($dvprint_x!='') $this->ContentType='print-detailview';
1292 if($Print_x!='') $this->ContentType='print-tableview';
1293 if($PrintDV!='') $this->ContentType='print-detailview';
1294
1295 // call detail view javascript hook file if found
1296 $dvJSHooksFile=dirname(__FILE__).'/hooks/'.$this->TableName.'-dv.js';
1297 if(is_file($dvJSHooksFile) && ($this->ContentType=='detailview' || $this->ContentType=='tableview+detailview')){
1298 $this->HTML.="\n<script src=\"hooks/{$this->TableName}-dv.js\"></script>\n";
1299 }
1300 }
1301 }